/* Socks Server 5
 * Copyright (C) 2002 - 2011 by Matteo Ricchetti - <matteo.ricchetti@libero.it>

 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */


#include"SS5Main.h"
#include"SS5Mod_dump.h"
#include"SS5Utils.h"

char *ss5ver=SS5_VERSION;

UINT InitModule( struct _module *m )
{
  m->AddDump     = AddDump;
  m->FreeDump    = FreeDump;
  m->GetDump     = GetDump;
  m->OpenDump    = OpenDump;
  m->WritingDump = WritingDump;
  m->CloseDump   = CloseDump;
  m->SrvDump     = SrvDump;
  m->ListDump    = ListDump;

  return OK;
}

/**
 * @brief 将dump列表S5DumpList中的数据发送到参数指定的客户端。每个dump结构组织为
 * "%16lu\n%2u\n%16lu\n%5u\n%5u\n%1u\n%1u\n"的形式。
 * 
 * @param s  socks5客户端连接到本服务器的socket，将向这个socket发送dump列表。
 * @return UINT 成功返回OK，失败返回ERR。
 * @todo 可能要加一个结束标记，这样客户端方便知道数据已结束。
 */
UINT ListDump( UINT s)
{
  UINT count;

  struct _S5DumpNode *node, *lnode;

  char buf[53];

  for(count = 0;count < MAXDUMPLIST; count++) {
    if( (node=S5DumpList[count]) != NULL) {

      lnode=node;
      do {
        if(lnode != NULL ) {
          snprintf(buf,sizeof(buf),"%16lu\n%2u\n%16lu\n%5u\n%5u\n%1u\n%1u\n", lnode->DstAddr,lnode->Mask,lnode->DstPort,
             lnode->DstRangeMin,lnode->DstRangeMax,lnode->DumpDir,lnode->DumpMode);
          lnode=lnode->next;
        }

        if( send(s,buf,sizeof(buf),0) == -1) {
          perror("Send err:");
          return ERR;
        }

      } while( lnode != NULL );
    }
  }
  return OK;
}

/**
 * @brief 处理sssrv发来的和DUMP有关的命令。即list,add和del操作，分别列出当前的dump任务，增加dump任务和删除dump任务。
 * 
 * @param ci sssrv客户端信息，我们需要用到它的Socket成员以收发数据。
 * @param sd socks5代理协议数据缓存（注意它不是代理工作时的网络IO缓存），请求存放在MethodRequest中。
 * @return INT 成功返回OK，无法识别的命令返回ERR_SRV_REQUEST（-1），操作失败返回ERR(0).
 */
INT SrvDump( struct _SS5ClientInfo *ci, struct _SS5Socks5Data *sd )
{
  UINT dstMask,udumpMode,uDumpDir;

  char destAddr[64],strPortRange[16],cdumpDir[1],cdumpMode[1], srvResponse[16];
  // 首条收发的数据，除第一个字节放在ver中剩余字节都存放在MethodRequest中。
  if( STREQ(sd->MethodRequest,"ET /list=DUMP HTTP/1.",sizeof("ET /list=DUMP HTTP/1.") - 1) ) {
    ListDump(ci->Socket);
    return OK;
  }
  else if( STREQ(sd->MethodRequest,"DD /dump=",sizeof("DD /dump=") - 1) ) {

    sscanf(sd->MethodRequest,"DD /dump=%64s\n%16s\n%1s\n%1s\n",destAddr,strPortRange,cdumpDir,cdumpMode);
    dstMask=S5GetNetmask(destAddr);
    switch(cdumpMode[0]) {
      case 'r':     udumpMode=RX;     break;
      case 't':     udumpMode=TX;     break;
      case 'b':     udumpMode=RTX;     break;
      default:      udumpMode=RX;     break;
    }

    switch(cdumpDir[0]) {
      case 's':     uDumpDir=0;     break;
      case 'd':     uDumpDir=1;     break;
      default:      uDumpDir=0;     break;
    }

    // 注:ASCII码表 64 = '@',65='A'。这里的判断略显简陋。
    if( destAddr[0] > 64 ) {
      // 如果是字符串形式的destAddr
      if( AddDump(ONLINE,S5StrHash(destAddr),S5GetRange(strPortRange),uDumpDir,udumpMode,32-dstMask) && (NDumpList < MAXDUMPLIST)) {
        strncpy(srvResponse,"OK\0",sizeof("OK\0"));
        NDumpList++;
        SS5SocksOpt.IsDump = OK;
      }
      else
        strncpy(srvResponse,"ERR\0",sizeof("ERR\0"));
    }
    else {
      // 如果是数字格式的destAddr
      if( AddDump(ONLINE,inet_network(destAddr),S5GetRange(strPortRange),uDumpDir,udumpMode,32-dstMask) && (NDumpList < MAXDUMPLIST)) {
        strncpy(srvResponse,"OK\0",sizeof("OK\0"));
        NDumpList++;
        SS5SocksOpt.IsDump = OK;
      }
      else
        strncpy(srvResponse,"ERR\0",sizeof("ERR\0"));
    }

    if( send(ci->Socket,srvResponse,strlen(srvResponse),0) == -1) {
      perror("Send err:");
      return ERR;
    }
    return OK;
  }
  else if( STREQ(sd->MethodRequest,"DEL /dump=",sizeof("DEL /dump=") - 1) ) {

    sscanf(sd->MethodRequest,"DEL /dump=%64s\n%16s\n%1s\n",destAddr,strPortRange,cdumpMode);

    dstMask=S5GetNetmask(destAddr);

    if( destAddr[0] > 64 ) {
      if( DelDump(S5StrHash(destAddr),S5GetRange(strPortRange),32-dstMask) && (NDumpList < MAXDUMPLIST)) {
        strncpy(srvResponse,"OK\0",sizeof("OK\0"));
        NDumpList++;
        SS5SocksOpt.IsDump = OK;
      }
      else
        strncpy(srvResponse,"ERR\0",sizeof("ERR\0"));
    }
    else {
      if( DelDump(inet_network(destAddr),S5GetRange(strPortRange),32-dstMask) && (NDumpList < MAXDUMPLIST)) {
        strncpy(srvResponse,"OK\0",sizeof("OK\0"));
        NDumpList++;
        SS5SocksOpt.IsDump = OK;
      }
      else
        strncpy(srvResponse,"ERR\0",sizeof("ERR\0"));
    }

    if( send(ci->Socket,srvResponse,strlen(srvResponse),0) == -1) {
      perror("Send err:");
      return ERR;
    }
    return OK;
  }

  return ERR_SRV_REQUEST;
}

/**
 * @brief 写入数据到dump文件。
 * 
 * @param df dump文件句柄。
 * @param pd socks代理网络收发缓冲区。
 * @param dumpMode RX(0),TX(1),RTX(2)之一。分别表示要记录“返回客户端的数据”，“从客户端来的数据”，“全部数据”
 * @return UINT 总是返回OK。
 */
UINT
WritingDump( FILE *df, struct _SS5ProxyData *pd, UINT dumpMode )
{
  char headerTitle[128];

  static UINT tx=0;
  static UINT rx=0;
  
  /*
   * TX: 记录从客户端收到的数据，它将通过我们的代理程序往外发往服务器。
   * fd == 0表示数据流方向为: socks客户端=>本代理=>服务器
   */
  if( pd->Fd == 0 ) {
    if( (dumpMode == TX) || (dumpMode == RTX) ) {
      if( tx == 0 ) {
        sprintf(headerTitle,"\n------------------------------ TX SEGMENT ------------------------------\n");
        fwrite(headerTitle,sizeof(char),strlen(headerTitle),df);
        tx++;
        rx = 0;
      }

      fwrite(pd->Recv,sizeof(char),pd->TcpRBufLen,df);
    }
  } 
  /* RX */
  else {
    // pd->Fd == 1表示数据流方向为: socks客户端<=本代理<=服务器
    if( (dumpMode == RX) || (dumpMode == RTX) ) {
      if( rx == 0 ) {
        sprintf(headerTitle,"\n------------------------------ RX SEGMENT ------------------------------\n");
        fwrite(headerTitle,sizeof(char),strlen(headerTitle),df);
        rx++;
        tx = 0;
      }

      fwrite(pd->Recv,sizeof(char),pd->TcpRBufLen,df);
    }
  }
  return OK;
}

/**
 * @brief 以二进制写入方式打开此客户端用户对应的文件，文件名的命令方式是："时间.pid.用户名.trc"
 * dump文件所在的默认目录是"/var/log/ss5"。
 * 
 * @param df 创建的句柄，创建文件后通过此输出参数返回。
 * @param ci socks5用户信息。
 * @return UINT 成功返回OK，失败返回ERR，如果打开了日志开关错误信息会写入到日志中。
 */
UINT 
OpenDump( FILE **df, struct _SS5ClientInfo *ci )
{
  char logString[128];
  char dumpFileName[64];
  char timeLog[32];

  pid_t pid;

  time_t now; 
  now = time(NULL);

  /*
   *    Get child/thread pid
   */
  if( NOTTHREADED() )
    pid=getpid();
  else
    pid=(UINT)pthread_self();

  strftime(timeLog,sizeof(timeLog),"%d-%b-%Y-%H-%M-%S",localtime(&now));

  sprintf(dumpFileName,"%s/ss5.%s.%u.%s.trc",S5TracePath,timeLog,pid,ci->Username);

  if( (*df = fopen(dumpFileName,"wb")) == NULL ) {
    ERRNO(pid)
    return ERR;
  }
  else
    return OK;
}

/**
 * @brief 关闭DUMP文件。
 * 
 * @param df 文件句柄。
 * @return UINT 总是返回OK。
 */
UINT 
CloseDump( FILE *df )
{

  fflush(df);
  fclose(df);
  
  return OK;
}

/**
 * @brief 根据输入的IP地址和端口的值生成一个哈希值，这个哈希值用于索引Dump列表 
 * S5DumpList、_tmp_S5DumpList和_old_S5DumpList。
 * 
 * @param da IP地址
 * @param dp 端口
 * @return UINT 
 */
UINT S5DumpHash( ULINT da, UINT dp )
{
  register int idx;
  register int len;

  register long int hashVal = 0;

  char s[32];

  snprintf(s,sizeof(s) - 1,"%lu%u",da,dp);

  len = strlen(s);
  for(idx = 0; idx < len; idx++)
    hashVal = 37*hashVal + s[idx];

  hashVal %= MAXDUMPLIST;
  if(hashVal < 0)
    hashVal += MAXDUMPLIST;

  return hashVal;

}

/**
 * @brief Get the Dump object
 * 填充di->DumpMode和di->DumpDir
 * @param sa 
 * @param sp 
 * @param da 
 * @param dp 
 * @param di 
 * @return UINT 
 */
UINT GetDump(ULINT sa, UINT sp, ULINT da, UINT dp, struct _SS5DumpInfo *di)
{
  register UINT index,nm;

  register ULINT n_da;

  struct _S5DumpNode *node;

  /* Look for SRC DUMP */

  for(nm=0;nm<=32;nm++) {
    if( nm < 32)
      n_da=((sa >> nm) << nm);
    else
      n_da=0;

    index=S5DumpHash( n_da, sp );

    if( S5DumpList[index]!= NULL ) {
      node=S5DumpList[index];
      do {
        if( (node->DstAddr == n_da) && (node->Mask == (nm)) && (node->DstPort == sp) && (node->DumpDir == SRC_DUMP) ) {
          di->DumpMode=node->DumpMode;
          di->DumpDir=node->DumpDir;
          return OK;
        }
        node=node->next;
      } while(node != NULL );
    }
  }

  for(nm=0;nm<=32;nm++) {
    if( nm < 32)
      n_da=((sa >> nm) << nm);
    else
      n_da=0;

    index=S5DumpHash( n_da, 0 );

    if( S5DumpList[index]!= NULL ) {
      node=S5DumpList[index];
      do {
        if( (node->DstAddr == n_da) && (node->Mask == (nm)) && (sp >= node->DstRangeMin) && (sp <= node->DstRangeMax) && (node->DumpDir == SRC_DUMP) ) {
          di->DumpMode=node->DumpMode;
          di->DumpDir=node->DumpDir;
          return OK;
        }
        node=node->next;
      } while(node != NULL );
    }
  }

  /* Look for DST DUMP */

  for(nm=0;nm<=32;nm++) {
    if( nm < 32)
      n_da=((da >> nm) << nm);
    else
      n_da=0;

    index=S5DumpHash( n_da, dp );

    if( S5DumpList[index]!= NULL ) {
      node=S5DumpList[index];
      do {
        if( (node->DstAddr == n_da) && (node->Mask == (nm)) && (node->DstPort == dp) && (node->DumpDir == DST_DUMP) ) {
          di->DumpMode=node->DumpMode;
          di->DumpDir=node->DumpDir;
          return OK;
        }
        node=node->next;
      } while(node != NULL );
    }
  }

  for(nm=0;nm<=32;nm++) {
    if( nm < 32)
      n_da=((da >> nm) << nm);
    else
      n_da=0;

    index=S5DumpHash( n_da, 0 );

    if( S5DumpList[index]!= NULL ) {
      node=S5DumpList[index];
      do {
        if( (node->DstAddr == n_da) && (node->Mask == (nm)) && (dp >= node->DstRangeMin) && (dp <= node->DstRangeMax) && (node->DumpDir == DST_DUMP) ) {
          di->DumpMode=node->DumpMode;
          di->DumpDir=node->DumpDir;
          return OK;
        }
        node=node->next;
      } while(node != NULL );
    }
  }

  return ERR;
}

/**
 * @brief 在dump哈希表中寻找并移除参数对应的元素。
 * 
 * @param da 对应_S5DumpNode::DstAddr
 * @param dp 对应_S5DumpNode::DstPort
 * @param mask 对应_S5DumpNode::Mask
 * @return UINT 成功返回OK，如果不存在对应的元素则返回ERR。
 */
UINT DelDump(ULINT da, ULINT dp, UINT mask )
{
  int index;
  struct _S5DumpNode *node, *prevnode=NULL;

  if( dp > 65535 )
    index=S5DumpHash( da, 0 );
  else
    index=S5DumpHash( da, dp );


  node=S5DumpList[index];

  if( node == NULL )
    return ERR;

  if( (node->DstAddr == da) && (node->Mask == mask) && (dp == node->DstPort) ) {
    if( node->next == NULL ) {

      free(node);
      S5DumpList[index]=NULL;
      return OK;
    }
    else {
      S5DumpList[index]=node->next;
      free(node);
      return OK;
    }
  }

  while( node->next != NULL ) {
    prevnode=node;
    node=node->next;

    if( (node->DstAddr == da) && (node->Mask == mask) && (dp == node->DstPort) ) {
      if( node->next != NULL )
        prevnode->next=node->next;
      else
        prevnode->next=NULL;

      free(node);
      node=NULL;

      return OK;
    }
  }
  return ERR;
}

/**
 * @brief 添加一个_S5DumpNode元素到列表。
 * 
 * @param ctx       ONLINE（0）或者OFFLINE（1），根据这个值填充元素到S5DumpList列表或者_tmp_S5DumpList列表。
 * @param da        字符串形式IP的HASH值或者IP的整形格式。
 * @param dp        端口范围。下限=LOW16(dp), 上限=HIGH16(dp);
 * @param dumpDir   dump目录，有效值为[0,1]之一。
 * @param dumpMode  dump模式，有效值为[0,1,2]之一。
 * @param mask      客户端IP的子网掩码。
 * @return UINT 成功返回OK，失败返回ERR。
 */
UINT AddDump(UINT ctx, ULINT da, ULINT dp, UINT dumpDir, UINT dumpMode, UINT mask )
{
  int index;
  struct _S5DumpNode *node, *prevnode;

  // 注意：dp < 65535时表示指定一个端口，dp > 65535时表示指定一个端口范围。
  if( dp > 65535 )
    index=S5DumpHash( da, 0 );
  else
    index=S5DumpHash( da, dp );
  // OFFLINE放_tmp_S5DumpList列表，ONLINE放S5DumpList列表。
  if( ctx == OFFLINE )
    node=_tmp_S5DumpList[index];
  else
    node=S5DumpList[index];
  // 如果哈希表中这个坑存在元素则将新的元素挂到此元素的next链表后。
  // 下边的操作近似于c++中的unordered_map<_S5DumpNode*>::push_back()
  // 这也是为什么我不喜欢用C，真的很麻烦。 
  // :-)
  if( node == NULL ) {
    if( (node=(struct _S5DumpNode *)calloc(1,sizeof(struct _S5DumpNode))) == NULL )
      return ERR;
    // 填充新元素结构体的各项成员的值
    node->Mask=mask;
    node->DstAddr=da;

    if( dp > 65535 ) {
      node->DstPort=dp;
      //dp > 65535时表示指定一个端口范围。
      //下面代码等于DstRangeMin=LOW16(dp), DstRangeMax=HIGH16(dp);
      node->DstRangeMax=dp;
      node->DstRangeMax >>= 16;
      node->DstRangeMax <<= 16;
      node->DstRangeMin = dp - node->DstRangeMax;
      node->DstRangeMax >>= 16;
    }
    else
      node->DstPort=dp;

    node->DumpDir=dumpDir;
    node->DumpMode=dumpMode;
    node->next=NULL;

    if( ctx == OFFLINE )
      _tmp_S5DumpList[index]=node;
    else
      S5DumpList[index]=node;
  }
  else {
    if( ctx == OFFLINE )
      node=_tmp_S5DumpList[index];
    else
      node=S5DumpList[index];

    do {
      if( (node->DstAddr == da) && (node->Mask == mask) && (node->DstPort == dp) ) {
        return ERR;
      }
      prevnode=node;
      node=node->next;

    } while(node != NULL );

    if( (node=(struct _S5DumpNode *)calloc(1,sizeof(struct _S5DumpNode))) == NULL )
      return ERR;

    node->Mask=mask;
    node->DstAddr=da;

  if( dp > 65535 ) {
      //dp > 65535时表示指定一个端口范围。
      //下面代码等于DstRangeMin=LOW16(dp), DstRangeMax=HIGH16(dp);
      node->DstPort=dp;
      node->DstRangeMax=dp;
      node->DstRangeMax >>= 16;
      node->DstRangeMax <<= 16;
      node->DstRangeMin = dp - node->DstRangeMax;
      node->DstRangeMax >>= 16;
    }
    else
      node->DstPort=dp;

    node->DumpMode=dumpMode;
    node->DumpDir=dumpDir;
    node->next=NULL;

    prevnode->next=node;
  }
  return OK;
}

/**
 * @brief 释放整个_S5DumpNode链表的元素，此元素用_S5DumpNode::next串在一起。
 * 
 * @param node 指向需要释放的_S5DumpNode链表。
 * @return UINT 总是返回OK。
 */
UINT FreeDump( struct _S5DumpNode **node )
{
  struct _S5DumpNode *lnode;
  struct _S5DumpNode *lnode_prev=NULL;

  lnode=*node;

  if( lnode != NULL ) {
    do {
      while( lnode->next != NULL ) {
        lnode_prev=lnode;
        lnode=lnode->next;
      }
      free(lnode);
      if( lnode_prev != NULL ) {
        lnode_prev->next=NULL;
        lnode=lnode_prev;
        lnode_prev=NULL;
      }
      else
        lnode=NULL;
    } while( (lnode) != NULL );
  }
  *node=NULL;

  return OK;

}

